using System;
using System.Collections.Generic;
using System.Text;

namespace Serialization
{
    static class TypeUtil
    {
        private static readonly Dictionary<Type, int> _primitiveTypes = new Dictionary<Type, int>();

        public const int TypeCodeTimeSpan = 100;
        public const int TypeCodeGuid = 101;
        public const int TypeCodeIntPtr = 102;

        static TypeUtil()
        {
            _primitiveTypes.Add(typeof(string), (int)TypeCode.String);
            _primitiveTypes.Add(typeof(DateTime), (int)TypeCode.DateTime);
            _primitiveTypes.Add(typeof(decimal), (int)TypeCode.Decimal);
            _primitiveTypes.Add(typeof(TimeSpan), TypeCodeTimeSpan);
            _primitiveTypes.Add(typeof(Guid), TypeCodeGuid);
            _primitiveTypes.Add(typeof(IntPtr), TypeCodeIntPtr);
        }

        public static int GetTypeCode(Type objType)
        {
            TypeCode tc = Type.GetTypeCode(objType);
            if (_primitiveTypes.ContainsKey(objType))
            {
                return _primitiveTypes[objType];
            }
            return (int)tc;
        }

        public static TypeInfo DetermineObjectType(object obj)
        {
            TypeInfo ret = TypeInfo.ObjectNull;
            if (obj != null)
            {
                Type objType = obj.GetType();
                if (objType.IsArray)
                {
                    ret = TypeInfo.Array;
                    objType = objType.GetElementType();
                }
                if (IsPrimitiveType(objType))
                {
                    ret |= TypeInfo.PrimitiveType;
                }
                else if (objType.IsValueType)
                {
                    ret |= TypeInfo.ValueType;
                }
                else
                {
                    ret |= TypeInfo.ReferenceType;
                }
                
            }
            return ret;
        }

        public static Stack<Type> GetTypeStack(Type objType)
        {
            Stack<Type> typeStack = new Stack<Type>();
            Type baseType = objType;
            while (baseType != typeof(object))
            {
                typeStack.Push(baseType);
                baseType = baseType.BaseType;
            }
            return typeStack;
        }

        public static bool IsPrimitiveType(Type objType)
        {
            return objType.IsPrimitive || _primitiveTypes.ContainsKey(objType);
        }

        public static bool IsReferenceType(TypeInfo typeInfo)
        {
            return ((byte)typeInfo & (byte)TypeInfo.ReferenceType) != 0;
        }

        public static bool IsArray(TypeInfo typeInfo)
        {
            return ((byte)typeInfo & (byte)TypeInfo.Array) != 0;
        }

        public static bool IsPrimitiveType(TypeInfo typeInfo)
        {
            return ((byte)typeInfo & (byte)TypeInfo.PrimitiveType) != 0;
        }

        public static bool IsValueType(TypeInfo typeInfo)
        {
            return ((byte)typeInfo & (byte)TypeInfo.ValueType) != 0;
        }


        public static bool IsObjectNull(TypeInfo typeInfo)
        {
            return typeInfo == TypeInfo.ObjectNull;
        }

        public static Type TypeFromTypeCode(int typeCode)
        {
            switch ((TypeCode)typeCode)
            {
                case TypeCode.Byte:
                    return typeof(byte);
                case TypeCode.SByte:
                    return typeof(sbyte);
                case TypeCode.Char:
                    return typeof(char);
                case TypeCode.Boolean:
                    return typeof(bool);
                case TypeCode.Int16:
                    return typeof(short);
                case TypeCode.UInt16:
                    return typeof(ushort);
                case TypeCode.Int32:
                    return typeof(int);
                case TypeCode.UInt32:
                    return typeof(uint);
                case TypeCode.Int64:
                    return typeof(long);
                case TypeCode.UInt64:
                    return typeof(ulong);
                case TypeCode.Single:
                    return typeof(float);
                case TypeCode.Double:
                    return typeof(double);
                case TypeCode.String:
                    return typeof(string);
                case TypeCode.Decimal:
                    return typeof(decimal);
                case TypeCode.DateTime:
                    return typeof(DateTime);
                case (TypeCode)TypeUtil.TypeCodeTimeSpan:
                    return typeof(TimeSpan);
                case (TypeCode)TypeUtil.TypeCodeIntPtr:
                    return typeof(IntPtr);
                case (TypeCode)TypeUtil.TypeCodeGuid:
                    return typeof(Guid);
                case TypeCode.DBNull:
                    return typeof(DBNull);
                case TypeCode.Object:
                    return typeof(object);
                default:
                    return null;
            }
        }

        public static ISerializationSurrogate FindSurrogate(Type objType, ISurrogateSelector selector, out ISurrogateSelector selectedSelector)
        {
            selectedSelector = null;
            if (selector != null)
            {
                ISerializationSurrogate surrogate = selector.GetSurrogate(objType, out selectedSelector);
                if (objType.IsGenericType && surrogate == null)
                {
                    Type gtd = objType.GetGenericTypeDefinition();
                    surrogate = selector.GetSurrogate(gtd, out selectedSelector);
                }
                return surrogate;
            }
            return null;
        }
    }
}
